<!doctype html>
<html>
<head>
<meta http-equiv='Content-Type' content='text/html; charset=UTF-8'>
<title>Mediasoup Client</title>
<style type="text/css">
	.room {
		cursor: pointer;
	} 
</style>
<script src="/socket.io/socket.io.js"></script>
<script type='text/javascript'>
let socket = null;
let localVideo = null;
let remoteContainer = null;
let stateSpan = null;
let localStream = null;
let peerConnection = null;
let usePlanB = false;
let roomId = null;

function init() {
	localVideo = document.getElementById('local_video');
	remoteContainer = document.getElementById('remote_container');
	stateSpan = document.getElementById('state_span');

	if (window.window.webkitRTCPeerConnection) {
		usePlanB = true;
	}
	
	navigator.getUserMedia	= navigator.getUserMedia
		|| navigator.webkitGetUserMedia 
		|| navigator.mozGetUserMedia 
		|| navigator.msGetUserMedia;
	
	RTCPeerConnection = window.RTCPeerConnection 
		|| window.webkitRTCPeerConnection 
		|| window.mozRTCPeerConnection;
	
	RTCSessionDescription = window.RTCSessionDescription 
		|| window.webkitRTCSessionDescription 
		|| window.mozRTCSessionDescription;

	initSocketIo();

	updateView();
}

function initSocketIo() {
	socket = io();
	
	socket.on('error', function(err){
		console.error('Socket.io error:', err);
	});

	socket.on('list', function(rooms){
		console.log('Receive [list]: ', rooms);
		listRooms(rooms);
	});

	socket.on('room-created', function(room){
		console.log('Receive [room-created]: ', room);
		joinNewRoom(room.id, room.name);
	});

	socket.on('offer', function(sdp){
		console.log('Receive [offer]: ', sdp);
		let offer = new RTCSessionDescription({
			type: 'offer',
			sdp: sdp
		});
		setOffer(offer);
	});

	socket.on('answer', function(sdp){
		console.log('Receive [answer]: ', sdp);
		let answer = new RTCSessionDescription({
			type: 'answer',
			sdp: sdp
		});
		setAnswer(answer);
	});

	send('list');
}

function sendOffer(sdp) {
	send('join', {
		planb: usePlanB,
		roomId: roomId,
		sdp: sdp
	});
}

function sendAnswer(answer) {
	send('joined', answer);
}

function send(type, data) {
	console.log('Sending [' + type + ']: ', data);
	socket.emit(type, data);
};

function appendRoom(id, name) {
	let roomsDiv = document.getElementById('rooms');

	var newDiv = document.createElement("div"); 
	newDiv.classList.add('room');
	newDiv.onclick = function(){
		joinRoom(id);
	};

	var newContent = document.createTextNode(name);

	newDiv.appendChild(newContent);
	roomsDiv.appendChild(newDiv);
}

function joinNewRoom(id, name) {
	appendRoom(id, name);
	joinRoom(id);
}

function joinRoom(id) {
	roomId = id;
	startVideo();
}

function listRooms(rooms) {
	for(let id in rooms) {
		appendRoom(id, rooms[id]);
	}
}

function createRoom() {
	let element = document.getElementById('newRoom');
	let name = element.value;
	if(!name.length) {
		return alert('Please specify room name');
	}
	
	send('create-room', name);
}

function startVideo() {
	getDeviceStream({video: true, audio: true})
	.then(function (stream) { // success
		localStream = stream;
		logStream('Local stream', stream);
		playVideo(localVideo, stream);
		connect();
		updateView();
	}).catch(function (error) { // error
		console.error('getUserMedia error:', error);
		return;
	});
}

function stopVideo() {
	pauseVideo(localVideo);
	stopLocalStream(localStream);
	localStream = null;

	updateView();
}

function stopLocalStream(stream) {
	let tracks = stream.getTracks();
	if (! tracks) {
		console.warn('NO tracks');
		return;
	}
	
	for (let track of tracks) {
		track.stop();
	}
}

function getDeviceStream(option) {
	if ('getUserMedia' in navigator.mediaDevices) {
		return navigator.mediaDevices.getUserMedia(option);
	}
	else {
		return new Promise(function(resolve, reject){		
			navigator.getUserMedia(option,
				resolve,
				reject
			);
		});			
	}
}

function playVideo(element, stream) {
	if ('srcObject' in element) {
		element.srcObject = stream;
	}
	else {
		element.src = window.URL.createObjectURL(stream);
	}
	element.play();
	element.volume = 0;
}

function pauseVideo(element) {
	element.pause();
	if ('srcObject' in element) {
		element.srcObject = null;
	}
	else {
		if (element.src && (element.src !== '') ) {
			window.URL.revokeObjectURL(element.src);
		}
		element.src = '';
	}
}

function prepareNewConnection() {
	let pc_config = {'iceServers':[]};
	let peer = new RTCPeerConnection(pc_config);

	if ('ontrack' in peer) {
		peer.ontrack = function(event) {
			let stream = event.streams[0];
			logStream('Remote stream on track', stream);
			if ( (stream.getVideoTracks().length > 0) && (stream.getAudioTracks().length > 0) ) {
				addRemoteVideo(stream.id, stream);
			}
			
		};
	}
	else {
		peer.onaddstream = function(event) {
			let stream = event.stream;
			logStream('Remote stream added', stream);
			
			addRemoteVideo(stream.id, stream);
		};
	}
	
	peer.onicecandidate = function (evt) {
		if (evt.candidate) {
			console.log(evt.candidate);
		} else {
			console.log('Empty ICE event');
		}
	};
	peer.onnegotiationneeded = function(evt) {
		console.log('Negotiation needed');
	};

	peer.onicecandidateerror = function (evt) {
		console.error('ICE candidate ERROR:', evt);
	};
	peer.onsignalingstatechange = function() {
		console.log('Signaling state changed: ' + peer.signalingState);
	};
	peer.oniceconnectionstatechange = function() {
		console.log('ICE connection state changed: ' + peer.iceConnectionState);
		showState('ICE connection state changed: ' + peer.iceConnectionState);
		if (peer.iceConnectionState === 'disconnected') {
			console.log('ICE disconnected');
			hangUp();
		}
	};
	peer.onicegatheringstatechange = function() {
		console.log('ICE gathering state changed: ' + peer.iceGatheringState);
	};
	
	peer.onconnectionstatechange = function() {
		console.log('Connection state changed: ' + peer.connectionState);
	};
	peer.onremovestream = function(event) {
		console.log('Stream removed');
		let stream = event.stream;
		removeRemoteVideo(stream.id, stream);
	};
	
	if (localStream) {
		console.log('Adding local stream');
		peer.addStream(localStream);
	}
	else {
		console.warn('No local stream found, continue anyway.');
	}
	return peer;
}

function makeOffer() {
	peerConnection = prepareNewConnection();
	peerConnection.createOffer({
		offerToReceiveAudio : 1,
		offerToReceiveVideo : 1
	})
	.then(function (sessionDescription) {
		console.log('Offer created');
		sendOffer(sessionDescription.sdp);
	})
	.catch(function(err) {
		console.error(err);
	});
}

function setOffer(sessionDescription) {
	if (peerConnection) {
		console.log('Peer connection alreay exist, reuse it');
	}
	else {
		console.log('Create new Peer connection');
		peerConnection = prepareNewConnection();
	}
	peerConnection.setRemoteDescription(sessionDescription)
	.then(function() {
		console.log('Set remote description');
		makeAnswer();
	}).catch(function(err) {
		console.error('Set remote description error: ', err);
	});
}

function makeAnswer() {
	console.log('Create remote session description' );
	if (! peerConnection) {
		console.error('Peer connection doesn\'t exist');
		return;
	}
	
	peerConnection.createAnswer()
	.then(function (sessionDescription) {
		console.log('Create answer');
		return peerConnection.setLocalDescription(sessionDescription);
	}).then(function() {
		let answer = peerConnection.localDescription;
		sendAnswer(answer);
	}).catch(function(err) {
		console.error(err);
	});
}

function setAnswer(sessionDescription) {
	if (! peerConnection) {
		console.error('Peer connection doesn\'t exist');
		return;
	}
	peerConnection.setRemoteDescription(sessionDescription)
	.then(function() {
		console.log('Set remote description');
	}).catch(function(err) {
		console.error('Set remote description error: ', err);
	});
}

function connect() {
	makeOffer();
	updateView();
}

function dissconnect() {
	send('quit');
	
	if (peerConnection) {
		console.log('Quiting');
		peerConnection.close();
		peerConnection = null;
		
		removeAllRemoteVideo();
	}
	else {
		console.warn('Peer doesn\'t exist');
	}

	stopVideo();
	updateView();
}

function showState(state) {
	stateSpan.innerText = state;
}

function logStream(msg, stream) {
	console.log(msg + ': id: ' + stream.id);

	let videoTracks = stream.getVideoTracks();
	if (videoTracks) {
	console.log('Video tracks length: ' + videoTracks.length);
	videoTracks.forEach(function(track) {
		console.log(' track id: ' + track.id);
	});
	}
	
	let audioTracks = stream.getAudioTracks();
	if (audioTracks) {
	console.log('Audio tracks length: ' + audioTracks.length);
	audioTracks.forEach(function(track) {
		console.log(' track id: ' + track.id);
	});
	}
}

function addRemoteVideo(id, stream) {
	let element = document.createElement('video');
	remoteContainer.appendChild(element);
	element.id = 'remote_' + id;
	element.width = 640;
	element.height = 480;
	element.srcObject = stream;
	element.play();
	element.volume = 0;
	element.controls = true;
}

function removeRemoteVideo(id, stream) {
	console.log('Remote video removed id: ' + id);
	let element = document.getElementById('remote_' + id);
	if (element) {
		element.pause();
		element.srcObject = null;
		remoteContainer.removeChild(element);
	}
	else {
		console.log('Remote video element not found');
	}
}

function removeAllRemoteVideo() {
	while (remoteContainer.firstChild) {
		remoteContainer.firstChild.pause();
		remoteContainer.firstChild.srcObject = null;
		remoteContainer.removeChild(remoteContainer.firstChild);
	}
}

function updateView() {
	if (peerConnection) {
		hideElement('roomsSelect');
		showElement('conference');
		enabelElement('disconnect_button');
	}
	else {
		showElement('roomsSelect');
		hideElement('conference');
		disableElement('disconnect_button');
	}
}

function enabelElement(id) {
	let element = document.getElementById(id);
	if (element) {
		element.removeAttribute('disabled');
	}
}

function disableElement(id) {
	let element = document.getElementById(id);
	if (element) {
		element.setAttribute('disabled', '1');
	}		
}

function hideElement(id) {
	let element = document.getElementById(id);
	if (element) {
		element.style.display = 'none';
	}
}

function showElement(id) {
	let element = document.getElementById(id);
	if (element) {
		element.style.display = 'block';
	}		
}
 
</script>
</head>
<body onload="init();">
	<h1>Mediasoup Client</h1>
	<div id="roomsSelect">
		<b>Rooms:</b>
		<div id="rooms"></div>
		<br/>
		<b>New Room:</b>
		<input type="text" id="newRoom" />
		<input type="button" value="Join" onclick="createRoom();" />
	</div>
	<div id="conference">
		<button id="disconnect_button" onclick="dissconnect();">Disconnect</button>
		<div>
			Local video<br />
			<video id="local_video" autoplay style="width: 160px; height: 120px; border: 1px solid black;"></video>
			<br/>
			<span id="state_span"></span>
		</div>
		Remote videos
		<br />
		<div id="remote_container"></div>
	</div>
</body>
</html>
